`include "mycpu.h"
module exe_stage(
    input clk,
    input reset,
    //MEM stage allowin
    input ms_allowin,
    //EXE stage allowin
    output es_allowin,
    //ID to EXE
    input ds_to_es_valid,
    input [`DS_TO_ES_WD - 1:0] ds_to_es_bus,
    //EXE to MEM
    output es_to_ms_valid,
    output [`ES_TO_MS_WD - 1:0] es_to_ms_bus,
    //data sram interface
    output data_sram_en,
    output [3:0] data_sram_wen,
    output [31:0] data_sram_addr,
    output [31:0] data_sram_wdata,
    //to ds for depend
    output out_es_valid,
    //exp12:
    input exec_flush
);

reg es_valid;
wire es_ready_go;
reg [`DS_TO_ES_WD - 1:0] ds_to_es_bus_r;
wire [31:0] es_alu_src1;
wire [31:0] es_alu_src2;
wire [11:0] es_alu_op;
wire [31:0] es_alu_result;
wire [31:0] es_rj_value;
wire [31:0] es_rkd_value;
wire [31:0] es_imm;
wire [31:0] es_pc;
wire [4:0] es_dest;
wire es_src1_is_pc;
wire es_src2_is_imm;
wire es_res_from_mem;
wire es_dst_is_r1;
wire es_gr_we;
wire es_mem_we;
//exp10,11
wire [31:0] es_result;
wire [32:0] es_mult_src1;
wire [32:0] es_mult_src2;
wire [65:0] es_mult_result_all;
wire [31:0] es_mult_result;
wire [63:0] es_divu_out;
wire [63:0] es_div_out;
wire [31:0] es_div_result;
wire [31:0] es_mod_result;
wire [1:0] es_ls_vaddr;
wire es_ld_unsigned;
wire es_op_ls_b;
wire es_op_ls_h;
wire es_op_unsigned;
wire es_op_mul;
wire es_op_mulh;
wire es_op_div;
wire es_op_mod;
wire es_op_divmod_u;
wire es_op_divmod;
wire es_div_in_ready, es_divu_in_ready;
wire es_div_divisor_ready, es_div_dividend_ready;
reg es_div_divisor_valid, es_div_dividend_valid;
wire es_divu_divisor_ready, es_divu_dividend_ready;
reg es_divu_divisor_valid, es_divu_dividend_valid;
wire es_div_out_valid, es_divu_out_valid;
reg es_div_shkhnd, es_divu_shkhnd;
//exp12
wire es_inst_csrrd;
wire es_inst_csrwr;
wire es_inst_csrxchg;
wire es_inst_syscall;
wire [14:0] es_ex_code;
wire es_inst_ertn;
wire [13:0] es_csr_num;
reg es_pre_ex;
wire es_ex = es_inst_syscall | es_inst_ertn;
always @(posedge clk) begin
    if(reset)
        es_pre_ex <= 1'b0;
    else if(~exec_flush & es_ex)
        es_pre_ex <= 1'b1;
    else if(exec_flush)
        es_pre_ex <= 1'b0;
end

assign es_op_divmod = (es_op_div | es_op_mod) & ~es_op_unsigned;
assign es_op_divmod_u = (es_op_div | es_op_mod) & es_op_unsigned;

//mul, mulh_w, mulh_wu
assign es_mult_src1 = {~es_op_unsigned & es_rj_value[31], es_rj_value[31:0]};
assign es_mult_src2 = {~es_op_unsigned & es_rkd_value[31], es_rkd_value[31:0]};
assign es_mult_result_all = $signed(es_mult_src1) * $signed(es_mult_src2);
assign es_mult_result = es_op_mul ? es_mult_result_all[31:0] : es_mult_result_all[63:32];
//div_w, diw_wu
assign es_div_result = es_op_unsigned ? es_divu_out[63:32] : es_div_out[63:32];
assign es_mod_result = es_op_unsigned ? es_divu_out[31:0] : es_div_out[31:0];

assign es_div_in_ready = ~es_op_unsigned & (es_div_divisor_ready & es_div_dividend_ready);
assign es_divu_in_ready = es_op_unsigned & (es_divu_divisor_ready & es_divu_dividend_ready);
always @(posedge clk) begin
    if(reset) begin
        es_div_shkhnd <= 1'b0;
        es_div_divisor_valid <= 1'b0;
        es_div_dividend_valid <= 1'b0;
    end
    else if(es_op_divmod & ~es_div_shkhnd) begin
        es_div_shkhnd <= 1'b1;
        es_div_divisor_valid <= 1'b1;
        es_div_dividend_valid <= 1'b1;
    end
    else if(es_div_shkhnd & es_div_in_ready) begin
        es_div_divisor_valid <= 1'b0;
        es_div_dividend_valid <= 1'b0;
    end
    else if(es_div_shkhnd & es_div_out_valid) begin
        es_div_shkhnd <= 1'b0;
    end
end

always @(posedge clk) begin
    if(reset) begin
        es_divu_shkhnd <= 1'b0;
        es_divu_divisor_valid <= 1'b0;
        es_divu_dividend_valid <= 1'b0;
    end
    else if(es_op_divmod_u & ~es_divu_shkhnd) begin
        es_divu_shkhnd <= 1'b1;
        es_divu_divisor_valid <= 1'b1;
        es_divu_dividend_valid <= 1'b1;
    end
    else if(es_divu_shkhnd & es_divu_in_ready) begin
        es_divu_divisor_valid <= 1'b0;
        es_divu_dividend_valid <= 1'b0;
    end
    else if(es_divu_shkhnd & es_divu_out_valid) begin
        es_divu_shkhnd <= 1'b0;
    end
end

mydiv my_signed_div(
    .aclk(clk),
    //divisor
    .s_axis_divisor_tdata(es_rkd_value),
    .s_axis_divisor_tready(es_div_divisor_ready),
    .s_axis_divisor_tvalid(es_div_divisor_valid),
    //dividend
    .s_axis_dividend_tdata(es_rj_value),
    .s_axis_dividend_tready(es_div_dividend_ready),
    .s_axis_dividend_tvalid(es_div_dividend_valid),
    //output result
    .m_axis_dout_tdata(es_div_out),
    .m_axis_dout_tvalid(es_div_out_valid)
);
mydivu my_signed_divu(
    .aclk(clk),
    //divisor
    .s_axis_divisor_tdata(es_rkd_value),
    .s_axis_divisor_tready(es_divu_divisor_ready),
    .s_axis_divisor_tvalid(es_divu_divisor_valid),
    //dividend
    .s_axis_dividend_tdata(es_rj_value),
    .s_axis_dividend_tready(es_divu_dividend_ready),
    .s_axis_dividend_tvalid(es_divu_dividend_valid),
    //result
    .m_axis_dout_tdata(es_divu_out),
    .m_axis_dout_tvalid(es_divu_out_valid)
);


assign out_es_valid = es_valid;

assign es_ready_go = (es_op_divmod & es_div_out_valid) |
                     (es_op_divmod_u & es_divu_out_valid) |
                     (~es_op_div & ~es_op_mod);

assign es_allowin = !es_valid || es_ready_go && ms_allowin;

assign es_to_ms_valid = es_valid && es_ready_go;

assign es_to_ms_bus[`ES_TO_MS_WD - 1:0] = {
    es_ex_code[14:0],      //173:159
    es_rj_value[31:0],      //158:127
    es_rkd_value[31:0],     //126:95
    es_inst_syscall,        //94:94
    es_inst_ertn,           //93:93
    es_inst_csrrd,          //92:92
    es_inst_csrwr,          //91:91
    es_inst_csrxchg,        //90:90
    es_csr_num[13:0],       //89:76
    es_ls_vaddr[1:0],       //75:74
    es_ld_unsigned,         //73:73
    es_op_ls_b,             //72:72
    es_op_ls_h,             //71:71
    es_pc[31:0],            //70:39
    es_result[31:0],        //38:7
    es_res_from_mem,        //6:6
    es_gr_we,               //5:5
    es_dest[4:0]            //4:0
};

always @(posedge clk) begin
    if(reset) 
        es_valid <= 1'b0;
    else if(exec_flush)
        es_valid <= 1'b0;
    else if(es_allowin)
        es_valid <= ds_to_es_valid;
end

always @(posedge clk) begin
    if(es_allowin && ds_to_es_valid)
        ds_to_es_bus_r <= ds_to_es_bus;
end
assign {
    es_ex_code[14:0],
    es_inst_syscall,
    es_inst_ertn,
    es_inst_csrrd,
    es_inst_csrwr,
    es_inst_csrxchg,
    es_csr_num,
    es_ld_unsigned,
    es_op_ls_b,
    es_op_ls_h,
    es_op_unsigned,
    es_op_mul,
    es_op_mulh,
    es_op_div,
    es_op_mod,
    es_pc[31:0],
    es_rj_value[31:0],
    es_rkd_value[31:0],
    es_imm[31:0],
    es_src1_is_pc,
    es_src2_is_imm,
    es_res_from_mem,
    es_gr_we,
    es_mem_we,
    es_dest[4:0],
    es_alu_op[11:0] } = ds_to_es_bus_r[`DS_TO_ES_WD - 1:0];

assign es_alu_src1 = es_src1_is_pc  ? es_pc[31:0] : es_rj_value;
assign es_alu_src2 = es_src2_is_imm ? es_imm : es_rkd_value;

alu alu(
    .alu_op     (es_alu_op    ),
    .alu_src1   (es_alu_src1  ),
    .alu_src2   (es_alu_src2  ),
    .alu_result (es_alu_result)
);

assign es_result = {32{es_op_mul | es_op_mulh}} & es_mult_result[31:0] |
                   {32{es_op_mod}} & es_mod_result[31:0] |
                   {32{es_op_div}} & es_div_result[31:0] |
                   {32{~es_op_mul & ~es_op_mulh & ~es_op_div & ~es_op_mod}} & es_alu_result[31:0];
assign es_ls_vaddr[1:0] = es_alu_result[1:0];

assign data_sram_en = ~exec_flush;
wire dsram_wvalid, es_ls_w;
assign dsram_wvalid = es_mem_we & es_valid & ~exec_flush & ~es_pre_ex;
assign es_ls_w = ~es_op_ls_b & ~es_op_ls_h;
assign data_sram_wen[0] = dsram_wvalid & ((es_ls_vaddr == 2'b00 & ~es_ls_w) | es_ls_w);
assign data_sram_wen[1] = dsram_wvalid & ((es_ls_vaddr == 2'b00 & es_op_ls_h) | 
                                          (es_ls_vaddr == 2'b01 & es_op_ls_b) |    
                                           es_ls_w);
assign data_sram_wen[2] = dsram_wvalid & ((es_ls_vaddr == 2'b10 & es_op_ls_h) | 
                                          (es_ls_vaddr == 2'b10 & es_op_ls_b) |    
                                           es_ls_w);
assign data_sram_wen[3] = dsram_wvalid & ((es_ls_vaddr == 2'b10 & es_op_ls_h) | 
                                          (es_ls_vaddr == 2'b11 & es_op_ls_b) |    
                                           es_ls_w);
assign data_sram_addr = es_alu_result;
assign data_sram_wdata = es_op_ls_b ? {4{es_rkd_value[7:0]}}  :
                         es_op_ls_h ? {2{es_rkd_value[15:0]}} :
                                      es_rkd_value[31:0];
 


endmodule